package pl.com.qapps.datastore.berkeley;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import pl.com.qapps.datastore.berkeley.impl.SinglePropertyKeyCreator;
import pl.com.qapps.datastore.berkeley.impl.TransactionImpl;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.appengine.api.datastore.Key;
import com.google.apphosting.api.DatastorePb.GetResponse;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import com.google.storage.onestore.v3.OnestoreEntity.Reference;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.ForeignKeyDeleteAction;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.Transaction;

/**
 * 
 * @author Jakub Dykowski
 * 
 */
class Store implements FileStore {

	private static final String ENTITIES_DB = "entities.db";
	private static final String KIND_DB = "kind.db";
	private static final String CLASS_DB = "classes.db";
	private static final String ID_DB = "id.db";
	private static final String ASC_DB = "asc.db";
	private static final String DESC_DB = "desc.db";
	private final Environment env;
	private final Database entityDb;
	private final Database idDb;
	private final SecondaryDatabase kindDb;
	private final SecondaryDatabase ascDb;
	private final SecondaryDatabase descDb;
	private final StoredClassCatalog classCatalog;

	public Store(File directory) throws DatabaseException {

		

		

		

		

		

		SecondaryConfig ascCfg = new SecondaryConfig();
		ascCfg.setAllowCreate(true);
		ascCfg.setTransactional(true);
		ascCfg.setSortedDuplicates(true);
		ascCfg.setKeyPrefixing(true);
		ascCfg.setForeignKeyDeleteAction(ForeignKeyDeleteAction.CASCADE);
		ascCfg.setMultiKeyCreator(new SinglePropertyKeyCreator(
				EntityBindings.ENTITY_BINDING, false));
		// ascCfg.setBtreeComparator(COMPARATOR);

		ascDb = env.openSecondaryDatabase(null, ASC_DB, entityDb, ascCfg);

		SecondaryConfig descCfg = new SecondaryConfig();
		descCfg.setKeyPrefixing(true);
		descCfg.setAllowCreate(true);
		descCfg.setTransactional(true);
		descCfg.setSortedDuplicates(true);
		descCfg.setForeignKeyDeleteAction(ForeignKeyDeleteAction.CASCADE);
		descCfg.setMultiKeyCreator(new SinglePropertyKeyCreator(ENTITY_BINDING,
				true));
		// descCfg.setBtreeComparator(COMPARATOR);
		descDb = env.openSecondaryDatabase(null, DESC_DB, entityDb, descCfg);

	}

	/**
	 * 
	 */
	@Override
	public void close() throws DatabaseException, IllegalStateException {

		ascDb.close();
		descDb.close();
		kindDb.close();
		entityDb.close();
		idDb.close();

		classCatalog.close();
	}

	public StoredClassCatalog getClassCatalog() {
		return classCatalog;
	}

	public Database getEntityDb() {
		return entityDb;
	}

	public Database getIdDb() {
		return idDb;
	}

	public Database getKindDb() {
		return kindDb;
	}

	public Environment getEnvironment() {
		return env;
	}

	public SecondaryDatabase getAscDb() {
		return ascDb;
	}

	public SecondaryDatabase getDescDb() {
		return descDb;
	}

	public Iterable<EntityProto> get(
			com.google.apphosting.api.DatastorePb.GetRequest get) {
		// TODO correct it
		GetResponse res;
		System.out.println("DatastoreHandler: get");
		for (Reference ref : get.keys()) {
			System.out.println("	" + ref.getPath());
			System.out.println("	"
					+ Arrays.toString(ref.getPath().toByteArray()));
			TupleOutput out = new TupleOutput();
			ref.getPath().outputTo(out);
			System.out.println("	" + Arrays.toString(out.toByteArray()));
			out = new TupleOutput();
			try {
				ref.getPath().writeTo(out);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			System.out.println("	" + Arrays.toString(out.toByteArray()));
		}

		return null;
	}

	@Deprecated
	public Iterable<Entity> get(Iterable<Key> keys, TransactionImpl txn) {
		return new IterableWrapper<EntityProto, Entity>(getInternal(
				new IterableWrapper<Key, DatabaseEntry>(keys) {

					@Override
					protected DatabaseEntry wrap(Key item) {
						DatabaseEntry entry = new DatabaseEntry();
						ENTITY_KEY_BINDING.objectToEntry(item, entry);
						return entry;
					}
				}, txn != null ? txn.raw : null)) {

			@Override
			protected Entity wrap(EntityProto item) {
				return EntityTranslator.createFromPb(item);
			}

		};
	}

	public Iterable<EntityProto> getInternal(Iterable<DatabaseEntry> keysData,
			Transaction txn) {
		List<EntityProto> entities = new LinkedList<>();
		for (DatabaseEntry keyData : keysData) {
			System.out.println("DatastoreHandler: get using key "
					+ new String(keyData.getData()) + " length:"
					+ keyData.getSize());
			DatabaseEntry entityData = new DatabaseEntry();
			OperationStatus result = entityDb.get(txn, keyData, entityData,
					LockMode.READ_COMMITTED);
			if (result == OperationStatus.SUCCESS) {
				EntityProto entity = ENTITY_BINDING.entryToObject(keyData,
						entityData);
				entities.add(entity);
			}
		}
		return entities;
	}
}
